package coin

import (
	"global/model"
	"time"
	"global/libs/structs"
	"global/core"
	"github.com/garyburd/redigo/redis"
	"github.com/ngaut/log"
	"strings"
	"github.com/kataras/iris/core/errors"
	"sync"
	"github.com/shopspring/decimal"
	"strconv"
)

type coinUsdPriceChannelData struct {
	ExchangeName 			string
	Coin 					string
	UsdPrice 				*structs.CoinUsdPrice
}

// 1. 加载所有币种和交易所信息
// 2. 根据币种和交易所数据提取需要的资料

type AnalysisCoin struct {

	Symbol 				string					// 查询的币种
	Pool 				*redis.Pool				// redis连接池
	Coin 				*model.Coin				// 币元信息
	Cmc 				structs.MarketQuote		// CMC获取的数据
	Symbols 			map[string]map[string]structs.Symbol 		// 所有交易对

	LastUpdated 		time.Time				// 最后更新时间

	wg 					sync.WaitGroup
	mux 				sync.Mutex

	ExitSignal 			chan interface{}
	UpdateCoinMetaData 	chan *model.Coin

	UsdPrice 			chan *coinUsdPriceChannelData
	AllExchangePrices 	map[string]*structs.CoinUsdPrice

	HC 					*CoinSort
	calculateTicker 	*time.Ticker
}

func NewAnalysisCoin(pool *redis.Pool,
	hc *CoinSort,
	coin *model.Coin,
	symbols map[string]map[string]structs.Symbol,
	markets map[string]map[string]interface{}) *AnalysisCoin {
	return &AnalysisCoin{
		Symbol: 				strings.ToLower(coin.Symbol),
		Pool: 					pool,
		HC: 					hc,
		Coin: 					coin,
		Symbols: 				symbols,
		ExitSignal: 			make(chan interface{}),
		UpdateCoinMetaData: 	make(chan *model.Coin),
		AllExchangePrices: 		make(map[string]*structs.CoinUsdPrice),

		UsdPrice: 				make(chan *coinUsdPriceChannelData),
		calculateTicker: 		time.NewTicker(2 * time.Second),
	}
}

func (a *AnalysisCoin) Start() {
	a.saveMetaData()
	for {
		select {
		case <-a.ExitSignal:
			return
		case updateCoin := <-a.UpdateCoinMetaData:
			a.mux.Lock()
			a.Coin = updateCoin
			a.mux.Unlock()
			a.saveMetaData()
		case exchangeCoinPrice := <- a.UsdPrice:
			go a.handleCoinPrice(exchangeCoinPrice)
		case <-a.calculateTicker.C:
			go a.calculate()
		}
	}

	log.Infof("exit coin analysis success")
}

func (a *AnalysisCoin) calculate()  {
	// 计算周期类，不允许操作AllCoins
	a.mux.Lock()
	defer a.mux.Unlock()

	if len(a.AllExchangePrices) == 0 {
		return
	}

	a.getMarketAndCoinInfo()

	// 计算平均价格
	ticker := a.avgPrice()

	coinTicker := a.getCoinTickerData(ticker)

	a.save(coinTicker)

	a.HC.receive <- coinTicker
}

func (a *AnalysisCoin) saveMetaData()  {
	conn := a.Pool.Get()
	defer conn.Close()

	// Xorm 不支持 redis 的反射
	coinBase := structs.CoinBase{
		Id: 			a.Coin.Id,
		Mid: 			a.Coin.Mid,
		NameEn: 		a.Coin.NameEn,
		NameCn: 		a.Coin.NameCn,
		Symbol: 		a.Symbol,
		Slug: 			a.Coin.Slug,
		Logo: 			a.Coin.Logo,
		Category: 		a.Coin.Category,
		Tags: 			a.Coin.Tags,
		Description: 	a.Coin.Description,
		Country: 		a.Coin.Country,
		Website: 		a.Coin.Website,
		Explorer: 		a.Coin.Explorer,
		WhitePaper: 	a.Coin.WhitePaper,
		SourceCode: 	a.Coin.SourceCode,
		MessageBoard: 	a.Coin.MessageBoard,
		Chat: 			a.Coin.Chat,
		Announcement: 	a.Coin.Announcement,
		Reddit: 		a.Coin.Reddit,
		Twitter: 		a.Coin.Twitter,
		MaxSupply: 		a.Coin.MaxSupply,
		DateAdded: 		a.Coin.DateAdded,
	}

	// 将保存币种静态数据，如果有强制更新
	_, err := core.SaveCoinBaseInfoIfNotExists(conn, coinBase)
	if err != nil {
		log.Errorf("Error: %s", err)
	}
}

func (a *AnalysisCoin) save(t *structs.CoinTicker) {

	conn := a.Pool.Get()
	defer conn.Close()

	// 保存 coin ticker 数据到 redis
	err := core.SaveCoinTickerInfo(conn, t)
	if err != nil {
		log.Errorf("Error: %s", err)
	}

	// 保存时间戳和最新价格，用于计算24h涨跌幅
	if err := core.SaveTsAndPriceMap(conn, a.Symbol, t.Close, a.LastUpdated); err != nil {
		log.Errorf("Error: %s", err)
	}

	// save to db
	_, err = model.CoinTickerModel.InsertCoinTicker(a.Coin.Mid, t)
	if err != nil {
		log.Errorf("Error: %s", err)
	}
}

// 整理参数
func (a *AnalysisCoin) getCoinTickerData(t *structs.CoinTicker) (*structs.CoinTicker) {

	a.LastUpdated = time.Now()

	// 计算流通量
	c := decimal.NewFromFloat(a.Cmc.CirculatingSupply)
	vol, _ := decimal.NewFromString(t.Volume)
	price, _ := decimal.NewFromString(t.Close)

	turnoverRate := decimal.Zero
	if !vol.Equal(decimal.Zero) {
		turnoverRate = vol.Div(c)
	}

	return &structs.CoinTicker{
		Symbol: 			a.Symbol,
		Volume:				t.Volume,
		Amount:				t.Amount,
		Open: 				t.Open,
		High: 				t.High,
		Low: 				t.Low,
		Close: 				t.Close,
		Percent: 			a.getPercent(t.Close),
		TotalSupply: 		decimal.NewFromFloat(a.Cmc.TotalSupply).StringFixedBank(8),
		CirculatingSupply:	decimal.NewFromFloat(a.Cmc.CirculatingSupply).StringFixedBank(8),
		// 市值=价格*流通量，流通量用coinmarket的可以
		MarketValue: 		c.Mul(price).StringFixedBank(8),
		// 换手率=成交量/流通量，这个分子分母也可以从coinmarket上获取数据
		TurnoverRate: 		turnoverRate.StringFixedBank(4),
		USD: 				t.Close,
		NumExchange: 		int32(len(a.AllExchangePrices)),
		NumMarketPairs: 	t.NumMarketPairs,
		LastUpdated: 		a.LastUpdated.Unix(),
	}
}

// 24小时涨跌幅百分比
func (a *AnalysisCoin) getPercent(price string) string {
	conn := a.Pool.Get()
	defer conn.Close()

	defaultPercent := decimal.Zero.StringFixedBank(4)

	yesterdayTs := a.LastUpdated.AddDate(0, 0, -1)
	ts := yesterdayTs.Unix()

	targetPrice, targetTs, err := core.GetTs24hAgo(conn, a.Symbol, ts)
	if err != nil {
		log.Errorf("Error: %s", err)
		return defaultPercent
	}

	if targetPrice.Equal(decimal.Zero) {
		return defaultPercent
	}

	nowPrice, _ := decimal.NewFromString(price)
	percent := nowPrice.Sub(targetPrice).Div(targetPrice)

	err = core.ClearOtherTsAndPrice(conn, a.Symbol, targetTs)
	if err != nil {
		log.Errorf("Error: clear other ts and price failed, err: %s", err)
	}

	return percent.StringFixedBank(4)
}

// 平均价格
func (a *AnalysisCoin) avgPrice() *structs.CoinTicker {
	counts := strconv.Itoa(len(a.AllExchangePrices))
	length, _ := decimal.NewFromString(counts)

	var o, h, l, c, vol decimal.Decimal
	var pairs int
	for _, v := range a.AllExchangePrices {
		o = o.Add(v.OpenUSD)
		h = h.Add(v.HighUSD)
		l = l.Add(v.LowUSD)
		c = c.Add(v.CloseUSD)
		vol = vol.Add(v.Volume24h)
		pairs = pairs + v.NumPairs
	}

	return &structs.CoinTicker{
		Open: 	o.Div(length).StringFixed(8),
		High: 	h.Div(length).StringFixed(8),
		Low: 	l.Div(length).StringFixed(8),
		Close: 	c.Div(length).StringFixed(8),
		Volume:	vol.StringFixedBank(8),
		Amount: vol.Mul(c).StringFixedBank(8),
		NumMarketPairs:  int32(pairs),
	}
}


// 保存交易所单个币种的价格
func (a *AnalysisCoin) handleCoinPrice(coinPrice *coinUsdPriceChannelData) {

	// get avg price
	a.mux.Lock()
	a.AllExchangePrices[coinPrice.ExchangeName] = coinPrice.UsdPrice
	a.mux.Unlock()
}

// 获取币种市场行情数据
func (a *AnalysisCoin) getMarketAndCoinInfo() error {
	conn := a.Pool.Get()
	defer conn.Close()
	marketInfo, err := core.GetCoinMarketInfo(conn, strings.ToLower(a.Coin.Symbol))
	if err != nil {
		return err
	}
	if marketInfo == nil {
		return errors.New("get coin market info return nil.")
	}

	quotes, err := core.GetMarketQuotesInfo(conn, a.Symbol)
	if err != nil {
		return err
	}
	a.Cmc.Quotes = quotes
	a.Cmc.MarketCoinBase = *marketInfo

	return nil
}